<?php

namespace app;

use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use xptech\jwt\exception\TokenInvalidException;
use sheep\exception\SheepException;
use sheep\exception\SheepDebugException;
use think\facade\Log;
use think\Response;
use Throwable;

/**
 * 应用异常处理类
 */
class ExceptionHandle extends Handle
{
    /**
     * 不需要记录信息（日志）的异常类列表
     * @var array
     */
    protected $ignoreReport = [
        HttpException::class,
        HttpResponseException::class,
        ModelNotFoundException::class,
        DataNotFoundException::class,
        ValidateException::class,
        SheepException::class,
        SheepDebugException::class,
    ];

    /**
     * Report or log an exception.
     *
     * @access public
     * @param  Throwable $exception
     * @return void
     */
    public function report(Throwable $exception): void
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @access public
     * @param \think\Request   $request
     * @param Throwable $e
     * @return Response
     */
    public function render($request, Throwable $e): Response
    {
        if ($e instanceof ValidateException) {  // 数据校验错误

            $result = [
                'error' => 1,
                'msg' => $e->getError(),
            ];
        } elseif ($e instanceof ModelNotFoundException) {

            $model = $e->getModel();
            $modelArr = explode('\\', $model);
            $modelName = (new $model)->getModelName();
            $modelName = $modelName ?: end($modelArr);
            $message = config('app.app_debug') ? "未找到" . $modelName . "数据" : "未找到数据";
            $result = [
                'error' => 1,
                'msg' => $message
            ];
        } elseif ($e instanceof DataNotFoundException) {

            $table = $e->getTable();
            if ($index = strpos($e->getTable(), '_')) {
                $table = substr($e->getTable(), ($index + 1));
            }
            $message = $table . '：未找到';
            $result = [
                'error' => 1,
                'msg' => $message
            ];
        } else if ($e instanceof HttpException) {

            $status_code = $e->getStatusCode();
            $message = $e->getMessage();
            if (!$message) {
                switch ($status_code) {
                    case 401:
                        $message = 'Unauthorized';
                        break;
                    case 403:
                        $message = 'Access Denied';
                        break;
                    case 404:
                        $message = 'Data Not Found';
                        break;
                    case 405:
                        $message = 'Method Not Allowed';
                        break;
                    case 500:
                        $message = 'Internal Server Error';
                        break;
                    default:
                        $message = 'Service Unavailable';
                }
            }
            $result = [
                'error' => 1,
                'msg' => $message
            ];
        } else if ($e instanceof HttpResponseException) {

            $status_code = $e->getResponse()->getCode();
            $message = $e->getMessage();
            if (!$message) {
                $result = $e->getResponse()->getContent();
                $result = is_array($result) ? json_encode($result) : $result;
                $message = $result ?? '响应失败';
            }
            $result = [
                'error' => 1,
                'msg' => $message
            ];
        } else if ($e instanceof TokenInvalidException) {

            $result = [
                'error' => 1,
                'msg' => '登录已过期',  // 其实是无效令牌,使用客户端返回统一话术
            ];
        } else if ($e instanceof SheepException) {

            $status_code = $e->status_code ?? 0;
            $error_code = $e->error_code ?? 1;
            $message = $e->getMessage() ?: "访问出错了";
            $result = ['error' => $error_code, 'msg' => $message];
        } elseif ($e instanceof SheepDebugException) {
        } else {
            $message = !empty($e->getMessage()) ? $e->getMessage() : "Has Handler Error in: " . get_class($e);
            $this->saveRequestLogInfo($request, $e);

            if (!config('app.app_debug')) {     // 如果正式环境，隐藏真实错误信息
                // 隐藏真实信息
                $result = ['error' => 1, 'msg' => config('app.error_message')];
            } else {
                // 真实错误信息
                $result = ['error' => 1, 'msg' => $message];
                // 其他错误交给系统处理
                // return parent::render($request, $e);     // 错误看日志文件，这里不再返回
            }
        }

        $status_code = $status_code ?? 200;
        $result['data'] = $result['data'] ?? null;

        if ($request->isJson() && !($e instanceof HttpResponseException)) {      // 如果是 accept = text/json, 返回json 相应
            return json($result, $status_code);
        }

        if (!config('app.app_debug')) {         // 错误页面
            $config = [];
            if (check_installed('', true)) {
                $config = \sheep\model\Config::getConfigs('basic.site');
            }
            return view(root_path() . 'sheep/view/error.html', ['result' => $result, 'status_code' => $status_code, 'config' => $config]);
        }

        // 其他错误交给系统处理
        return parent::render($request, $e);
    }

    /**
     * 获取路由信息
     * @return void
     */
    private function saveRequestLogInfo($request, $error): void
    {
        $requestInfo = [
            'ip' => $request->ip(),
            'method' => $request->method(),
            'host' => $request->host(),
            'uri' => $request->url(),
        ];

        $routeInfo = [];
        if ($request->rule()) {
            $routeInfo = [
                'rule' => $request->rule()->getRule(),
                'route' => $request->rule()->getRoute(),
                'option' => $request->rule()->getOption(),
                'var' => $request->rule()->getVars(),
            ];
        }

        $logInfo = [
            "========== REQUEST LOG INFO BEGIN ==========",
            '[ REQUEST ] ' . var_export($requestInfo, true),
            '[ ROUTE ] ' . var_export($routeInfo, true),
            '[ HEADER ] ' . var_export($request->header(), true),
            '[ PARAM ] ' . var_export($request->param(), true),
            '[ Message ] ' . var_export('[' . $error->getCode() . ']' . $error->getMessage(), true),
            '[ File ] ' . var_export($error->getFile() . ':' . $error->getLine(), true),
            // '[ Trace ] ' . var_export($error->getTrace(), true),
            "============================================= REQUEST LOG INFO ENDED ==========",
        ];

        $logInfo = implode(PHP_EOL, $logInfo) . PHP_EOL;
        Log::error($logInfo);
    }
}
